package com.sghy.xcxservice.common;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;

@Component
public class TokenInterceptor implements HandlerInterceptor {
    private static final String AUTH_HEADER = "Authorization";
    private static final String BEARER_PREFIX = "Bearer ";
    private static final int UNAUTHORIZED_STATUS = 401;

    private final HashSet<String> whitelist = new HashSet<>();

    public TokenInterceptor() {
        // 添加白名单
        whitelist.add("/user/login");
        whitelist.add("/user/loginOut");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (Objects.equals(request.getMethod(), "OPTIONS")){
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, X-Requested-With, remember-me");
            response.setStatus(200);
            return false;
        }
        String token = request.getHeader(AUTH_HEADER);
        String uri = request.getRequestURI();
        if (whitelist.contains(uri)){
            response.setStatus(200);
            return true;
        }else {
            if (token!=null){
                String tokenValue = token.substring(BEARER_PREFIX.length());
                JWTUtil jwtUtil = new JWTUtil();
                if (StringUtils.isNotBlank(tokenValue) && jwtUtil.validateToken(tokenValue)){
                    String uid = jwtUtil.getUserInfoFromToken(tokenValue);
                    setUID(uid);
                    return true;
                }else{
                    response.setStatus(UNAUTHORIZED_STATUS);
                    return false;
                }
            }else {
                response.setStatus(UNAUTHORIZED_STATUS);
                return false;
            }
            
        }
    }
    
    /**
     * postHandle 是 Spring MVC 中的一个拦截器方法，用于在请求处理完成后但未返回响应前执行。
     * @param request 当前请求的 HttpServletRequest 对象。
     * @param response 当前请求的 HttpServletResponse 对象。
     * @param handler 当前请求所对应的处理器对象，即处理请求的 Controller 或处理器方法。
     * @param modelAndView 当前请求处理过程中产生的 ModelAndView 对象。它包含了视图名称、模型数据等信息，可以在这个方法中对它进行修改。
     * postHandle 方法在请求处理完成后但未返回响应前执行，它的主要作用是用于对响应进行修改、记录日志、统计执行时间等工作。在这个方法中可以对响应进行修改，如添加自定义的 HTTP Header、修改响应体的内容等。
     * 需要注意的是，postHandle 方法是在请求处理完成后但未返回响应前执行，因此它可以对响应进行修改，但不能修改请求的处理结果。如果需要对请求的处理结果进行修改，应该在 Controller 中对返回的数据进行处理。
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 包装响应对象
        CharResponseWrapper responseWrapper = new CharResponseWrapper(response);
        
        // 继续处理请求
        HandlerInterceptor.super.postHandle(request, responseWrapper, handler, modelAndView);
        
        // 获取响应的数据
        String responseData = responseWrapper.toString();
        byte[] responseDataBytes = responseData.getBytes(StandardCharsets.UTF_8);
        
        // 判断数据大小
        if (responseDataBytes.length > 1024) {
            // 使用 Gzip 进行压缩
            byte[] compressedData = compressData(responseData.getBytes());

            // 设置响应头标记使用了 Gzip 压缩
            response.setHeader("Content-Encoding", "gzip");

            // 将压缩后的数据写入响应
            response.getOutputStream().write(compressedData);
        } else {
            // 数据大小在阈值以下，直接写入响应
            response.getOutputStream().write(responseDataBytes);
        }
    
    
    }
    public static byte[] compressData(byte[] data) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
            gzipOutputStream.write(data);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return byteArrayOutputStream.toByteArray();
    }
    // 自定义 HttpServletResponseWrapper 以便获取响应数据
    private static class CharResponseWrapper extends HttpServletResponseWrapper {
        private final CharArrayWriter charArrayWriter = new CharArrayWriter();
        
        public CharResponseWrapper(HttpServletResponse response) {
            super(response);
        }
        
        @Override
        public PrintWriter getWriter() {
            return new PrintWriter(charArrayWriter);
        }
        
        @Override
        public String toString() {
            return charArrayWriter.toString();
        }
    }
    
    
    
    /**
     * afterCompletion 是 Spring MVC 中的一个拦截器方法，用于在请求处理完成后执行，无论是正常处理还是异常处理。它的定义如下：
     *
     * @param request  当前请求的 HttpServletRequest 对象。
     * @param response 当前请求的 HttpServletResponse 对象。
     * @param handler  当前请求所对应的处理器对象，即处理请求的 Controller 或处理器方法。
     * @param ex       当前请求处理过程中抛出的异常，如果没有异常则为 null
     *                 afterCompletion
     *                 方法在请求完成后（包括响应已经返回给客户端后）执行，它的主要作用是用于清理资源、记录日志等工作。在这个方法中可以进行一些清理性的操作，如关闭数据库连接、清除缓存等。
     *                 需要注意的是，afterCompletion 方法是在请求处理完成后才会执行，因此它无法对请求的处理结果进行修改。如果需要在请求处理过程中对响应进行修改，应该使用 postHandle 方法。
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        clear();
    }

    private static final ThreadLocal<String> USER_ID_HOLDER = new ThreadLocal<>();

    public static void setUID(String uid) {
        USER_ID_HOLDER.set(uid);
    }

    public static String getUid() {
        return USER_ID_HOLDER.get();
    }

    public static void clear() {
        USER_ID_HOLDER.remove();
    }

}



